Skip to content

Jotai 状态管理

在大型项目中使用 Jotai 管理状态的优势是:原子化、组合性强、灵活易维护。以下我们以一个典型的 Todo List 为例,逐步说明如何在大型项目中使用 Jotai 管理全局状态,并给出组织结构和操作方法。


🧠 一、Jotai 优势与适用场景(简述)

在大型项目中,Jotai 适用于以下场景:

  • 状态高度模块化(例如:多个列表页、多种筛选条件、分页、用户偏好等)
  • 多个组件共享状态,但不想用 Redux 那样繁重的样板代码
  • 需要异步、派生状态(Derived state)管理
  • 希望按需加载或懒加载部分状态模块

🗂️ 二、项目结构组织建议(模块化)

bash
src/
  ├── atoms/
   ├── todos.ts            # 所有 todo 的 atoms 和相关状态
   └── filters.ts          # 筛选条件状态
  ├── hooks/
   └── useTodoActions.ts   # 封装逻辑操作
  ├── components/
   ├── TodoList.tsx
   └── TodoItem.tsx
  └── App.tsx

🧩 三、定义状态 atoms(todos.ts)

ts
// atoms/todos.ts
import { atom } from 'jotai';

export type Todo = {
    id: number;
    text: string;
    completed: boolean;
};

// 全部 todos 列表
export const todosAtom = atom<Todo[]>([]);

// 派生:未完成 todos
export const incompleteTodosAtom = atom((get) => get(todosAtom).filter((t) => !t.completed));

🎛️ 四、定义筛选状态(filters.ts)

ts
// atoms/filters.ts
import { atom } from 'jotai';

export type Filter = 'all' | 'completed' | 'incomplete';

export const filterAtom = atom<Filter>('all');

🧮 五、组合派生状态:根据筛选显示 todo(组合 atom)

ts
// atoms/todos.ts 中追加
import { filterAtom } from './filters';

export const filteredTodosAtom = atom((get) => {
    const todos = get(todosAtom);
    const filter = get(filterAtom);
    switch (filter) {
        case 'completed':
            return todos.filter((t) => t.completed);
        case 'incomplete':
            return todos.filter((t) => !t.completed);
        default:
            return todos;
    }
});

🧰 六、封装操作逻辑(hooks/useTodoActions.ts)

ts
import { useAtom } from 'jotai';
import { todosAtom } from '@/atoms/todos';

let idCounter = 1;

export const useTodoActions = () => {
    const [todos, setTodos] = useAtom(todosAtom);

    const addTodo = (text: string) => {
        setTodos([...todos, { id: idCounter++, text, completed: false }]);
    };

    const toggleTodo = (id: number) => {
        setTodos(todos.map((t) => (t.id === id ? { ...t, completed: !t.completed } : t)));
    };

    const deleteTodo = (id: number) => {
        setTodos(todos.filter((t) => t.id !== id));
    };

    return { addTodo, toggleTodo, deleteTodo };
};

🧱 七、组件使用示例

TodoList.tsx

tsx
import { useAtomValue } from 'jotai';
import { filteredTodosAtom } from '@/atoms/todos';
import { TodoItem } from './TodoItem';

export const TodoList = () => {
    const todos = useAtomValue(filteredTodosAtom);
    return (
        <ul>
            {todos.map((todo) => (
                <TodoItem key={todo.id} todo={todo} />
            ))}
        </ul>
    );
};

TodoItem.tsx

tsx
import { Todo } from '@/atoms/todos';
import { useTodoActions } from '@/hooks/useTodoActions';

export const TodoItem = ({ todo }: { todo: Todo }) => {
    const { toggleTodo, deleteTodo } = useTodoActions();

    return (
        <li>
            <span
                style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
                onClick={() => toggleTodo(todo.id)}
            >
                {todo.text}
            </span>
            <button onClick={() => deleteTodo(todo.id)}>🗑</button>
        </li>
    );
};

⚙️ 八、扩展:异步状态、服务调用(示例)

ts
// 异步获取 todos 示例
import { atom } from 'jotai';
import { atomWithQuery } from 'jotai-tanstack-query'; // 或者 atomWithPromise

export const todosAsyncAtom = atomWithQuery(() => ({
    queryKey: ['todos'],
    queryFn: async () => {
        const res = await fetch('/api/todos');
        return res.json();
    }
}));

✅ 总结:大型项目中使用 Jotai 的技巧

技巧描述
模块化拆分 atoms每个功能拆分单独文件,避免一个大文件
使用 atomFamily 管理子项例如每个 todo 独立状态管理
封装操作逻辑成 hooks类似 useTodoActions,保持 UI 简洁
使用 atomWithStorage 持久化支持本地存储
异步状态用 atomWithQuery搭配 tanstack-query/React Query 使用
使用 Devtools 调试状态jotai-devtools 插件帮助调试